#include <stdlib.h>
#include <dos.h>
#include "PROCHAN.H"
#include "mpx_supt.h"

/* Global variables */
pcbBase ready;
pcbBase blocked;
unsigned char tempStack[SYS_STACK_SIZE];
unsigned short sp_save;
unsigned short ss_save;
unsigned short new_sp;
unsigned short new_ss;
pcb *cop;
params *param_ptr;
int terminalEventFlag;
int comEventFlag;
iod *tempIOD;
iocb *term, *com;

/** \brief Creates a new PCB and allocates memory for it.
	* Creates the PCB and allocates memory for its parameters and its stack.
	* @param memorySize int - amount of memory to allocate for program
	* @param offset int - offset for instruction pointer
	* \return pcb - pointer to the created PCB.
*/
pcb* allocatePCB(int memorySize, int offset, int stackSize){
	pcb *newPCB = (pcb *)sys_alloc_mem(sizeof(pcb));
	newPCB->stackBase = (unsigned char*)sys_alloc_mem(stackSize);
	newPCB->stackTop = (newPCB->stackBase + stackSize - sizeof(context));
	newPCB->stackSize = stackSize;
	newPCB->loadAddress = (unsigned char*)sys_alloc_mem(memorySize);
	newPCB->execAddress = newPCB->loadAddress + offset;
	return newPCB;
}

/** \brief Sets up the initial parameters in a new PCB
	* Sets up the name, priority, and process class with the information passed in and sets
	* state to ready and prev and next to NULL.
	* @param name string - the name of the PCB
	* @param priority int - the priority of the process
	* @param processClass int - the class of the process (APPLICATION / SYSTEM)
	* @param memorySize int - amount of memory to allocate for program
	* @param offset int - offset for instruction pointer
	* \return pcb - pointer to the created PCB.
*/
pcb* setupPCB(char *name, int priority, int processClass, int memorySize, int offset, int stackSize){
	pcb *newPCB;
	newPCB = allocatePCB(memorySize, offset, stackSize);
	if (newPCB != NULL){
		strcpy(newPCB->name, name);
		newPCB->priority = priority;
		newPCB->processClass = processClass;
		newPCB->state = SUSPENDED_READY;
		newPCB->prev = NULL;
		newPCB->next = NULL;
	}
	return newPCB;
}

/**
	* \brief Removes a PCB from its queue.
	* Removes a process control block from its current queue.
	* @param block pcb - the PCB to be removed from the queue.
*/
void removePCB(char *name){
	pcbBase *queue;
	pcb *current;
	pcb *next;
	pcb *prev;
	queue = &ready;
	current = queue->first;
	while (current != NULL){
		if(strcmp(current->name, name) == 0){
			break;
		}
		current = current->next;
	}
	if (current == NULL){
		queue = &blocked;
		current = queue->first;
		while(current != NULL){
			if(strcmp(current->name, name) == 0){
				break;
			}
			current = current->next;
		}
	}
	if (current != NULL){
		prev = current->prev;
		next = current->next;
		current->next = NULL;
		current->prev = NULL;
		if (next == NULL & prev == NULL){
			queue->first = NULL;
			queue->last = NULL;
			} else if (next == NULL){
			queue->last = prev;
			prev->next = NULL;
			} else if (prev == NULL){
			queue->first = next;
			next->prev = NULL;
			} else {
			prev->next = next;
			next->prev = prev;
		}
		queue->currCount -= 1;
	}
}

/** \brief This is the freePCB function that frees all memory associated with PCB.
	* Calls the sys_free_mem function in order to free all memory associated with PCB.
	* @param PCBPointer This parameter points to the PCB.
*/
void freePCB(pcb *PCBPointer) {
	sys_free_mem(PCBPointer->stackBase);
	sys_free_mem(PCBPointer->loadAddress);
	sys_free_mem(PCBPointer);
	PCBPointer = NULL;
}//close free_pcb

/**
	* \brief Finds and returns a PCB based on the given name.
	* Finds a process control block in either queue and returns a pointer to it.
	* @param PCBname string - name of the PCB to find.
	* @return pcb - returns a pointer to the PCB, or NULL if it does not exist.
*/
pcb* findPCB(char *PCBname)
{
	pcb *current;
	if(ready.first != NULL)
	{
		current = ready.first;
		
		while(current != NULL)
		{
			if(strcmp(current->name, PCBname)==0)
			{
				return current;
			}
			current = current->next;
		}
	}
	
	if(blocked.first != NULL)
	{
		current = blocked.first;
		
		while(current != NULL)
		{
			if(strcmp(current->name, PCBname)==0)
			{
				return current;
			}
			current = current->next;
		}
		
	}
	current = NULL;
	return current;
}

/**
	* \brief Inserts a PCB into a queue
	* Inserts a process control block into a queue based on its current state.
	* @param newPCB - pointer the pcb to be inserted
*/
void insertPCB(pcb *newPCB)
{
	pcb *current = NULL;
	if(newPCB->state == 3 | newPCB->state == 5)
	{
		if(blocked.first == NULL)
		{
			blocked.first = newPCB;
			blocked.last = newPCB;
		}
		else
		{
			blocked.last->next = newPCB;
			newPCB->prev = blocked.last;
			blocked.last = newPCB;
		}
		blocked.currCount += 1;
	}
	else//ready queue
    {
		if(ready.first == NULL)//if empty
		{
			ready.first = newPCB;
			ready.last = newPCB;
		}
		else
		{
			current = ready.first;
			while(current != NULL)
			{
				if(newPCB->priority > current->priority)
				{
					break;
				}
				current = current->next;
			}
			if(current == NULL)//if newPCB has lower priority than everything in the ready
			{
				newPCB->prev = ready.last;
				ready.last->next = newPCB;
				ready.last = newPCB;
			}
			else if(current->prev == NULL)//if current first
			{
				newPCB->next = current;
				ready.first = newPCB;
				current->prev = newPCB;
			}
			else//if current middle
			{
				newPCB->next = current;
				newPCB->prev = current->prev;
				newPCB->prev->next = newPCB;
				current->prev = newPCB;
			}
			ready.currCount += 1;
		}
	}
}


/** \brief Initializes prochan.
*/
void initprochan(){
	term = sys_alloc_mem(sizeof(iocb));
	com = sys_alloc_mem(sizeof(iocb));
	term->head = NULL;
	term->tail = NULL;
	term->count = 0;
	com->head = NULL;
	com->tail = NULL;
	com->count = 0;
	tempIOD = sys_alloc_mem(sizeof(iod));
	com_open(&comEventFlag, 1200);
	trm_open(&terminalEventFlag);
	sys_set_vec(sys_call);
}

/** \brief Finds the first pcb in the ready queue which is not suspended and returns it.
	* @return pcb
*/
pcb* getFirstReady(){
	pcb *temp = ready.first;
	while(temp != NULL){
		if (temp->state == READY){
			break;
		}
		else{
			temp = temp->next;
		}
	}
	return temp;
}

/** \brief Dispatcher
	* Interrupt. Switches to the next process to run and performs a context switch to the stack for that process.
*/
void interrupt dispatch(){
	if (sp_save == NULL){
		ss_save = _SS;
		sp_save = _SP;
	}
	cop = getFirstReady();
	if(cop == NULL){
		_SS = ss_save;
		_SP = sp_save;
	}
	else{
		removePCB(cop->name);
		cop->state = RUNNING;
		new_ss = FP_SEG(cop->stackTop);
		new_sp = FP_OFF(cop->stackTop);
		_SS = new_ss;
		_SP = new_sp;
	}
}

/** \brief System call Interrupt
	* Determines the operation code for the call and either sets the state to READY and places
	* it back in the ready queue or frees its memory. It then calls dispatch to switch to the
	* next process.
*/
void interrupt sys_call(){
	cop->stackTop = MK_FP(_SS, _SP);
	param_ptr = (params*)(cop->stackTop + sizeof(context));
	new_ss = FP_SEG(&tempStack);
	new_sp = FP_OFF(&tempStack) + SYS_STACK_SIZE;
	_SS = new_ss;
	_SP = new_sp;
	//start arrrrrrrr6
	trm_getc();
	
	if(terminalEventFlag == 1)
	{
		terminalEventFlag = 0;
		tempIOD = term->head;
		term->head = tempIOD->next;
		tempIOD->process->state = READY;
		removePCB(tempIOD->name);
		insertPCB(tempIOD->process);
		term->count--;
		sys_free_mem(tempIOD);
		if (term->head != NULL){
			switch(term->head->operation){
				case READ:
				trm_read(term->head->buff, term->head->count);
				break;
				case WRITE:
				trm_write(term->head->buff, term->head->count);
				break;
				case CLEAR:
				trm_clear();
				break;
				case GOTOXY:
				trm_gotoxy(0,0);
				break;
				default:
				break;
			}
		}
	}
	if(comEventFlag == 1)  //find this
	{
		comEventFlag = 0;
		tempIOD = com->head;
		com->head = tempIOD->next;
		tempIOD->process->state = READY;
		removePCB(tempIOD->name);
		insertPCB(tempIOD->process);
		com->count--;
		sys_free_mem(tempIOD);
		if (com->head != NULL){
			switch(com->head->operation){
				case READ:
				com_read(com->head->buff, com->head->count);
				break;
				case WRITE:
				com_write(com->head->buff, com->head->count);
				break;
				default:
				break;
			}
		}
		
	}
	//end arrrrrrrr6
	switch (param_ptr->op_code){
		case IDLE:
		cop->state = READY;
		insertPCB(cop);
		cop = NULL;
		break;
		case EXIT:
		freePCB(cop);
		cop = NULL;
		break;
		default:
		ioSceduler();
		break;
	}
	dispatch();
}

int loader(char *name, int priority, int stackSize){
	context *npc;
	int rc;
	pcb *newpcb;
	int prog_len_p;
	int start_offset_p;
	if(!findPCB(name)){
		sys_check_program("\0", name, &prog_len_p, &start_offset_p);
		if (prog_len_p == 0){
			output("Invalid program.\n", 1);
			return 1;
		}
		newpcb = setupPCB(name, priority, APPLICATION, prog_len_p, start_offset_p, stackSize);
		rc =sys_load_program(newpcb->loadAddress, prog_len_p, "", name);
		if (rc<0){
		output("File does not exist.\n",1);
		return 1;
		}
		npc = (context*)newpcb->stackTop;
		npc->IP = FP_OFF(newpcb->execAddress);
		npc->CS = FP_SEG(newpcb->execAddress);
		npc->FLAGS = 0x200;
		npc->DS = _DS;
		npc->ES = _ES;
		newpcb->state = READY;
		insertPCB(newpcb);
		}
		return 1;
		
		}
		
		void ioSceduler(){
		iod *newIOD;
		iocb *queue;
		newIOD = (iod*)sys_alloc_mem(sizeof(iod));
		newIOD->process = cop;
		newIOD->name = cop->name;
		newIOD->device = param_ptr->device_id;
		newIOD->operation = param_ptr->op_code;
		newIOD->buff = param_ptr->buf_addr;
		newIOD->count = param_ptr->count_addr;
		newIOD->next = NULL;
		if (newIOD->device == TERMINAL)
		queue = term;
		if (newIOD->device == COM_PORT)
		queue = com;
		if(queue->count == 0)
		{
		queue->head = newIOD;
		queue->tail = newIOD;
		queue->count = 1;
		processIORequest(newIOD,newIOD->device);
		}
		else 
		{
		queue->tail->next = newIOD;
		queue->tail = newIOD;
		queue->count++;
		}
		
		newIOD->process->state = BLOCKED; //check this
		insertPCB(newIOD->process);
		}
		
		void processIORequest(iod *tempIOD, int device)
		{
		if(tempIOD->operation == WRITE) //is it operation?
		{
		if(device == TERMINAL) //check name of terminal
		{
		trm_write(tempIOD->buff, tempIOD->count);
		}
		else if(device == COM_PORT) //check name of com port
		{
		com_write(tempIOD->buff, tempIOD->count);
		}
		}
		else if(tempIOD->operation == READ)
		{
		if(device == TERMINAL)
		{
		trm_read(tempIOD->buff, tempIOD->count);
		}
		else if(device == COM_PORT)
		{
		com_read(tempIOD->buff, tempIOD->count);
		}
		}
		else if(tempIOD->operation == CLEAR)
		{
		trm_clear();
		}
		else if(tempIOD->operation == GOTOXY)
		{
		trm_gotoxy(0,0);
		}
		}		